﻿using System;
using System.Collections.Generic;
using System.Linq;
using StockFinder.Model;
using log4net;
using StockFinder.Model.Constants;

namespace StockFinder.HistoricalPriceAnalysers
{
    /// <summary>
    /// Analyses the prices list for a latest close being the all time high close.
    /// Base is measured against the closes, rather than daily high and low
    /// </summary>
    public class AllTimeHighBreakoutPriceDatabaseAnalyser
    {
        private static readonly ILog _Log = LogManager.GetLogger(typeof(AllTimeHighBreakoutPriceDatabaseAnalyser));

        public HistoricPriceAnalyserResult AnalysePrices(List<DailyPrice> prices)
        {
            var result = new HistoricPriceAnalyserResult();

            if (prices.Count() > 0)
            {
                var orderedPrices = prices.OrderByDescending(p => p.PriceDate);

                //get last price
                DailyPrice lastPrice = orderedPrices.ElementAt(0);

                result.LastPriceDate = lastPrice.PriceDate;

                //make sure this has a price history, and not IPO day
                if (orderedPrices.Count() > 1)
                {
                    try
                    {
                        /* is the previous close higher than any previous high */

                        //get all time high before today
                        DailyPrice allTimeHigh = orderedPrices.Skip(1).AllTimeHighestPriceByAdjustedClose();

                        //is the last price close higher than the previous all time high?
                        if (lastPrice.AdjustedClose > allTimeHigh.AdjustedClose)
                        {
                            //yes we have broken out, now get base details
                            //4. get all prices since all time high
                            IEnumerable<DailyPrice> baseSinceAllTimeHigh = orderedPrices.Where(i => i.PriceDate >= allTimeHigh.PriceDate);

                            //5. make sure there are no 0 prices
                            if (!baseSinceAllTimeHigh.Any(i => i.AdjustedClose == 0))
                            {
                                TimeSpan timeInBase = lastPrice.PriceDate - allTimeHigh.PriceDate;

                                int daysInBase = timeInBase.Days;

                                //6. get the low of the base
                                decimal baseLow = baseSinceAllTimeHigh.Min(i => i.AdjustedClose);

                                //7. the daily price for that low
                                DailyPrice baseLowPrice = baseSinceAllTimeHigh.First(i => i.AdjustedClose == baseLow);

                                //determine the base depth
                                decimal baseDepth = (allTimeHigh.AdjustedClose - baseLow) / allTimeHigh.AdjustedClose * 100;

                                //intra day base low
                                decimal intraDayBaseLow = baseSinceAllTimeHigh.Min(i => i.AdjustedLow);

                                //avoid divide by 0
                                decimal intraDayBaseLowDepth = (intraDayBaseLow == 0) ? 0 : (allTimeHigh.AdjustedClose - intraDayBaseLow) / allTimeHigh.AdjustedClose * 100;

                                //check whether applied 50 SMA
                                if (lastPrice.DayIndicators.ContainsKey(DayIndicatorNames.CLOSE_SMA50))
                                {
                                    var sma50 = lastPrice.DayIndicators[DayIndicatorNames.CLOSE_SMA50];

                                    if (sma50 > 0)
                                    {
                                        decimal percentFromSMA50 = lastPrice.AdjustedClose - sma50;

                                        percentFromSMA50 = (percentFromSMA50 / sma50) * 100;

                                        result.PercentFromSMA50 = percentFromSMA50;
                                    }   
                                }                                

                                //set diagnostics
                                result.Succeeded = true;
                                result.BaseDepth = baseDepth;
                                result.IntraDayBaseDepth = intraDayBaseLowDepth;
                                result.BaseLength = (baseSinceAllTimeHigh.Count() - 1);
                                result.BaseLowPrice = baseLow;
                                result.BaseLowDate = baseLowPrice.PriceDate;
                                result.DaysOfPrices = orderedPrices.Count();
                                result.PreviousHighPrice = allTimeHigh.AdjustedClose;
                                result.PreviousHighDate = allTimeHigh.PriceDate;
                            }
                            else
                            {
                                _Log.Warn("Contains a 0 price, skipping");
                            }
                        }
                    }
                    catch (Exception exception)
                    {
                        _Log.Error(exception);
                    }
                }
            }
            else
            {
                _Log.Warn("No prices, skipping");
            }

            return result;
        }
    }
}
